iT邦幫忙

2023 iThome 鐵人賽

DAY 13
1
Software Development

CRUD仔的一生(上集)系列 第 14

[ACID] 二階段提交(2 Phase Commitment (2PC))

  • 分享至 

  • xImage
  •  

2PC (2 phase commit)

前言

前面介紹了 CAP 定理,得知了在分散式系統中,資料的同步只能 C3 取 2。

  • CA 資料庫非常常見,一般的 RDBMS 就是。
  • AP 資料庫也非常常見,需多的 NoSQL 就是。
  • CP (C-onsistency P-artition Tolerance): 強一致性且腦裂可正常運作,但不保證服務基本上保持可用

CP 確實是比較難以理解的一個選項,因此在此舉個 CP 的同步方式,

  1. C-onsistency 強一致性:
    想到的就是 RDB 的 tx。
  2. P-artition Tolerance 分區容錯:
    各個 DB 可以獨立作業,因已經符合強一致性,所以資料的讀取可以直接由第一個接觸的 db 直接服務。
  3. Non A-vailability 無服務基本上保持可用:
    因為必須維持強一致性,所以當有 write 的操作,就必須與每個 DB 都一起 write,全部成功後才能告知使用者成功。
    換句話說,當有其中一者無法成功,就無法維持強一致性,必須 retry 或回傳失敗。

因此,有一個簡單且有效的作法 2PC/3PC 可以幫我們達到以上三個目的。在介紹 2PC/3PC 前,
我們先來思考與試錯,一步一步的了解該項目的重點與解決的項目。

出發前說明

目標

讓兩個獨立的 DB 加入新的資料,並且一起成功或一起失敗,不會有其中一台 DB 成功,一台 DB 失敗。

Remark:
  • Atomic 是指同一個 tx 內,所有所下的 sql command 全部成功或全部敗。
  • Strong Consistency 是指 n 個獨立的 DB,所有所下的 sql command 全部成功或全部失敗,每台都一樣。

角色介紹

先回歸操作一台 db 的樣子,
當我們要操作 db 時,我們會先寫一個 server,讓該 server 連上 db 下 CRUD 等操作。
當我們要操作 n 座 db 時,一樣的寫一個 server,讓該 server 連上那些 db 下 CRUD 等操作。
如果以這個案例來定義,我們將會稱該 server 為協調者,而 db 就是我們的參與者。

  1. 協調者(Coordinator): 協調 n 台 db 的 CRUD 等操作。
  2. 參與者: 需要彼此溝通的 db。

故事情境

今天有兩台 db,分別為 A 與 B,A 插入一筆紀錄,B 也必須插入一筆一樣的紀錄。

嘗試

以下 sql 都是在協調者的操作

try1: 直接分別用兩個 tx 做 commit

time db A db B
1 begin
2 insert...
3 commit
4 begin
5 insert...
6 commit

非常正常的操作,先完成 db A 的操作,再去完成 db B 的操作。
但假設 time5 發生錯誤,db A 就無法 rollback,因為 connection A 的 tx 已經 commit 了,
必須使用手動刪除,兩 db 之間會有軟狀態(暫時性的不一致),與我們想要達成的強一致性不符合。

try2: 使用交錯 sql 方式做 commit

time db A db B
1 begin
2 begin
3 insert...
4 insert...
5 commit
6 commit

你一句我一句,看似兩者膠著狀態,但其實 time6 發生錯誤,也是如同 try1 一樣無法 rollback。

try3: 反省

不管如何,commit 時才發生錯誤都無法達到 rollback。
因為只要其中一個 commit 了,另一個發生錯誤就無法挽回了。
有沒有一個 sql 語句可以模擬 commit,提前知道錯誤,如果有錯誤就 rollback。
如果這個模擬 commit 沒發生錯誤,那麼我們就可以假設,接下來的 commit 基本上不會發生錯誤了。

你可能會想,通常 TX 到 COMMIT 之間,要發生錯誤早就發生了,甚麼錯誤會發生在下 COMMIT 之後呢?
其實最常見的就是違反 UNIQUE constraint,這裡稍微列出一些 COMMIT 之後才會發生的錯誤。

  1. 外鍵約束:當您插入或更新資料時,外鍵約束要求引用的目標行必須存在於關聯表中。 如果目標行在提交之前被刪除或修改,那麼在提交時會引發外鍵約束錯誤。
  2. 檢查約束:檢查約束用於定義欄位的驗證規則。 如果插入或更新資料時欄位的值不符合檢查約束條件,則不會在插入時報錯,但在提交事務之前可能會引發檢查約束錯誤。
  3. 並發問題:在多用戶環境中,另一個用戶可能會在您查詢或更新資料之間修改了相同的資料。 這種情況可能不會立即導致錯誤,但在提交交易時可能引發並發衝突或鎖定問題。
  4. 觸發器:觸發器是一種特殊類型的預存程序,它們會在表上的插入、更新或刪除操作發生時自動執行。 觸發器可以包含複雜的業務邏輯,可能不會立即報錯,但在執行觸發器時可能引發錯誤。
  5. 唯一性約束:雖然插入資料時會立即報錯,但如果另一個事務插入了相同的唯一鍵值,並且在您提交事務之前,您的插入操作可能會引發唯一性約束衝突錯誤。
  6. 物理意外:斷電或斷連線等使事情無法完成。

裡面我們撇除物理意外等不可控的因素外,希望至少能製造一個 SQL 語句可以模擬 commit,提前知道錯誤。

準備階段 prepare statement

希望至少能製造一個 SQL 語句可以模擬 commit,提前知道錯誤。
那麼我們就可以假設,接下來的 commit 基本上不會發生錯誤了。
這個需求,就是 2PC 的重點核心準備階段 prepare statement
一般的 tx 就是一個 commit 後直接見真章,是否發生錯誤無法事先得知,這就是我們一般俗稱的一階段提交 1PC。
而這裡我們將加上準備階段 prepare statement,是否發生錯誤透過準備階段事先得知,
就可以假設,接下來的 commit 基本上不會發生錯誤;如果發生錯誤,那麼就可以在 commit 之前做 rollback。
所以加上PREPARE這就是我們鼎鼎大名的 2PC(two-phase commit)啦!
而 3PC 也是相同概念,不過分成三個階段canCommitpreCommitdoCommit
不過由於 3PC 大部分的資料庫不支援,所以就不再此贅述了。

try4: 使用 2PC

time db A db B
1 begin
2 insert...
3 prepare ...
4 begin
5 insert...
6 prepare ...
6 commit
7 commit

加上了 prepare statement,就可以在 commit 前做個預檢查,確定了不會發生錯誤,在做 commit,大大減少在 commit 發生的錯誤。
不過 2PC 的缺點也是一樣,只能保證 commit 前的錯誤,只是增加一個 statement 來做預檢查。
如網路斷線或斷電,是 commit 後才發生錯誤就真的無法避免了。

以 Postgres 為例

上面我們知道了 2PC 的基本原理,再來就是直接上個真實案例,以便學習真正的落地。
請事先準備好 DB,postgres 的max_prepared_transactions記得打開設定參數,否則會使用 prepare.
在 Postgres 之中,prepare statement 為PREPARE TRANSACTION transaction_id,詳細介紹可以參考官方介紹

舉個例子

A 資料庫中的用戶 aa,轉 500 元到 B 資料庫用戶 bb。
需求:

  1. 轉帳時帳戶要有足夠的金額才能轉帳。
  2. 強一致性(不可有軟狀態)。
time db A db B
1 begin
2 update account set balance=balance-500 where id='aa;
3 PREPARE TRANSACTION 'foo';
4 begin
5 update account set balance=balance+500 where id='bb;
6 PREPARE TRANSACTION 'bar';
6 commit PREPARED 'foo'; / rollback PREPARED 'foo';
7 commit PREPARED 'bar'; / rollback PREPARED 'bar';

今天加上了PREPARE TRANSACTION,就可以在 commit 時提前發現問題並且 rollback,
便免其中一方事先 commit,造成無法挽回的後果。

2PC 推廣

我們知道 2PC 的重點其實就是加上prepare statement
那麼其實 2PC 的使用範圍就不只是 DB 了,甚至金流 api,不同類型的資料庫串接等等,
只要協調者需要維持強一致性的需求,那麼 2PC 就是一個很好的選擇。

結語

或許各位讀者早就已經在使用 2PC 了,只是當時不知道那是 2PC 而已,
如今需多 API 等概念都使用到了 2PC。
2PC 也可以當作共識演算法,因為要讓每個節點都能達到相同的值。
其實 2PC 也是 Paxos 算法的一種簡化版本。

參考資料

  1. Can a COMMIT statement (in SQL) ever fail? How?
  2. Understanding Prepared Transactions and Handling the Orphans
  3. 20. Handle Distributed Transactions | Two-Phase Commit (2PC), Three-Phase Commit (3PC), SAGA Pattern
  4. 2PC & 3PC
  5. Backend 台灣貼文
  6. PREPARE TRANSACTION
  7. PostgreSQL 两阶段提交事务 2PC(two-phase commit)
  8. 二階段提交(2PC)
  9. MySQL 对分布式事务(XA Transactions)的支持
  10. Day 12 - 共識演算法 - 3 Phase Commitment (3PC)

上一篇
[ACID] 其實也不用ACID
下一篇
[CREATE] Heap/Index Organized Table
系列文
CRUD仔的一生(上集)32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言